Además de las funciones incluidas en el lenguaje, y de la “gramática” para realizar todo tipo de gráficos que ofrece ggplot2, podemos agregar a nuestro repertorio paquetes adicionales que realizan visualizaciones específicas. En la vertiente de interactividad, son notables los paquetes reunidos bajo el nombre de htmlwidgets, que generan visualizaciones dinámicas con una gran variedad de estilos y recursos.
La particularidad de los htmlwidgets es que traen a R excelentes funciones de visualización desarrolladas en JavaScript. El gran fuerte de JavaScript es la generación de contenido interactivo para sitios web. Los paquetes reunidos en la colección htmlwidgets “envuelven” el código Javascript en instrucciones en R, haciendo un puente entre los dos mundos, y permitiendo generar desde R visualizaciones que pueden publicarse como contenido web. Para nuestros fines, vamos a concentrarnos en dos de las opciones en particular:leaflet para mapas interactivos y Plotly para convertir nuestros gráficos realizados en ggplot2 a versiones interactivas.
Con la explosión de de popularidad de los mapas online, con Google Maps al frente, se ha vuelto habitual explorar información geográfica en entornos interactivos, que permiten al usuario desplazarse libremente por la superficie terrestre y cambiar el nivel de zoom con el que se muestran los datos. Mapas con información tan precisa como la posición de los delitos, que incluso permite ver a parcela donde han ocurrido, se beneficia en extremo de la posibilidad de variar la escala de visualización a voluntad.
Desde R es fácil proyectar nuestros datos sobre mapas interactivos, usando el paquete leaflet.
Lo activamos con:
> library(leaflet)
A continuación, vamos a importar el paquete st para trabajar con datos geográficos y la tabla con la cantidad de homicidios por provincia en la región de la Patagonia que generamos la última clase.
> library(sf)
>
> tb2 <- read_sf("./data/homicidios_prov_patagonia.geojson")
El uso de leaflet es similar al de ggplot; uno toma un dataframe y lo muestra mediante capas que exponen distintos aspectos de la información. Para comenzar, usemos leaflet(tb2)
> leaflet(tb2)
… y no obtuvimos mucho. Tal como pasa con ggplot(), si uno no define ninguna capa de visualización, el resultado es una especie de lienzo vacío.
Siguiente paso: agregar un mapa base, agregando al código la función addTiles(). Para sumar capas a un mapa de leaflet usamos ” %>% ” en lugar del ” + ” que requiere ggplot(), pero el concepto es el mismo.
> leaflet(tb2)%>%
+ addTiles()
Hasta acá, tenemos un mapa del mundo pero sin nuestros polígonos. Para ello los vamos a agregar con la función addPolygons.
leaflet no nos permite identificar regiones en un mapa por color automáticamente, sino que requiere que creemos por nuestra cuenta una paleta de colores para aplicar a nuestros datos. suerte contamos con funciones auxiliares que nos permiten crear paletas a medida, dependiendo del tipo de datos que vamos a mostrar: colorFactor() para variables categóricas, colorNumeric() para variables numéricas, o colorQuantile() también para variables numéricas, pero agrupadas en cuantiles. Cualquiera de las opciones requiere al menos dos parámetros. Uno es “palette”, para definir los tonos a usar (aquí funcionan nuestros amigos viridis, magma, plasma e inferno, y también las paletas Brewer, como Set1 , Spectral o Accent). El parámetro restante es “domain”, que simplemente toma un vector con los datos que vamos a representar con la paleta. En este caso, le vamos a pasar NULL ya que los datos se los vamos a asignar en el mismo gráfico.
> pal <- colorNumeric("viridis", NULL)
Ahora, vamos a agregarle los polígonos y agregar algunos parámetros para rellenar los polígonos y que queden bien. fillColor = ~pal(n/Proyecciones_2020) indica el relleno. Con stroke = FALSE borramos las líneas que separan los polígonos, con smoothFactor = 0.3 suavizamos los bordes, con fillOpacity = 0.8 transparentamos un poco el relleno. Por último, con label = ~paste0(nam, ": ", round(n/Proyecciones_2020,2)) vamos a agregar una etiqueta que diga el valor redondeado de la relación homicidios dolosos/proyección poblacional.
> leaflet(tb2) %>%
+ addTiles() %>%
+ addPolygons(stroke = FALSE, smoothFactor = 0.3, fillOpacity = 0.8,
+ fillColor = ~pal(n_casos/proy),
+ label = ~paste0(nomprov, ": ", n_casos/proy))
¡Ups! Acá nos encontramos con otro problema, que mencionamos al principio de la clase anterior: las proyecciones. Estas no están en el formato que leaflet usa: Mercator (EPSG:4326), así que hay que convertirlas usando la función st_transform(). Vamos a pasara el parámetro "+init=epsg:4326" para pasar la latitud y longitud de estas coordenadas a la proyección correcta.
> tb2<-st_transform(tb2, crs = "+init=epsg:4326")
Y ahora sí, debería funcionar el gráfico:
> leaflet(tb2) %>%
+ addTiles() %>%
+ addPolygons(stroke = FALSE, smoothFactor = 0.3, fillOpacity = 0.8,
+ fillColor = ~pal(n_casos/proy),
+ label = ~paste0(nomprov, ": ", n_casos/proy))
Hasta ahora estuvimos trabajando con mapas cloropléticos. Sin embargo, también podríamos querer trabajar con un nivel de desagregación mayor, como viendo exactamente en qué coordenadas de latitud y longitud suceden los delitos. Esto lo podemos lograr marcando esos puntos en el mapa.
Para practicar, trabajaremos con un dataset publicado por la Ciudad Autónoma de Buenos Aires, con delitos registrados durante el 2020. Los datos fueron publicados por en el Mapa del delito de la Ciudad (https://mapa.seguridadciudad.gob.ar/). Los más de 70.000 registros del dataset podrían resultar “pesados” para un mapa interactivo si no disponemos de bastante memoria RAM, así que practicaremos con una parte del total: los siniestros viales ocurridos en diciembre.
> siniestros_viales <- st_read("./data/siniestros_caba.geojson")
## Reading layer `siniestros_caba' from data source
## `/home/laia/Escritorio/Cursos/min_seguridad_curso/clase7/data/siniestros_caba.geojson'
## using driver `GeoJSON'
## Simple feature collection with 729 features and 9 fields (with 729 geometries empty)
## Geometry type: GEOMETRYCOLLECTION
## Dimension: XY
## Bounding box: xmin: NA ymin: NA xmax: NA ymax: NA
## Geodetic CRS: WGS 84
>
> head(siniestros_viales)
## Simple feature collection with 6 features and 9 fields (with 6 geometries empty)
## Geometry type: GEOMETRYCOLLECTION
## Dimension: XY
## Bounding box: xmin: NA ymin: NA xmax: NA ymax: NA
## Geodetic CRS: WGS 84
## fecha franja tipo subtipo cantidad latitud longitud
## 1 2020-12-01 0 Lesiones Siniestro Vial NA -34.62524 -58.36199
## 2 2020-12-01 0 Lesiones Siniestro Vial NA -34.60880 -58.43871
## 3 2020-12-01 0 Lesiones Siniestro Vial NA NA NA
## 4 2020-12-01 4 Lesiones Siniestro Vial NA NA NA
## 5 2020-12-01 6 Lesiones Siniestro Vial NA -34.62777 -58.37960
## 6 2020-12-01 7 Lesiones Siniestro Vial NA -34.62816 -58.43477
## barrio comuna geometry
## 1 Boca 4 GEOMETRYCOLLECTION EMPTY
## 2 Caballito 6 GEOMETRYCOLLECTION EMPTY
## 3 NA GEOMETRYCOLLECTION EMPTY
## 4 NA GEOMETRYCOLLECTION EMPTY
## 5 Constitucion 1 GEOMETRYCOLLECTION EMPTY
## 6 Caballito 6 GEOMETRYCOLLECTION EMPTY
La primera parte del gráfico va a ser igual al primero que hicimos. Usamos leaflet(siniestros_viales) para hacer el mapa interactivo y addTiles() para pasar la capa del mundo. Pero también vamos a agregar la capa addMarkers(). Con ella, leaflet se encarga de buscar las columnas que contienen coordenadas, y si aparecen con nombres reconocibles en inglés (“latitude” y “longitude”, o “lat” y “lng”) las identifica automáticamente y sitúa en el mapa un pin por cada fila.
Si las coordenadas aparecen en columnas con otros nombres (así es en nuestro caso, con nombres en castellano tenemos longitud en vez de longitude), podemos cambiar los nombres a “lat” y “lng”, o dejar la data como está e indicarle a leaflet cuáles son los nombres vía parámetros. La capa a agregar sería addMarkers(lat = ~latitud, lng = ~longitud). Obsérvese que leaflet requiere usar el simbolillo “~” antepuesto a los nombres de columna; esto le indica que debe buscar esos nombres dentro del dataframe declarado dentro de la llamada inicial a leaflet().
> leaflet(siniestros_viales) %>%
+ addTiles() %>%
+ addMarkers(lat = ~latitud, lng = ~longitud)
¡Ya tenemos un mapa reconocible! Para mejorarlo, agregamos el parámetro “popup”, que permite mostrar información adicional ciando se cliquea sobre un pin. Por ejemplo, el barrio, contenido en la columna “barrio”):
> leaflet(siniestros_viales) %>%
+ addTiles() %>%
+ addMarkers(lat = ~latitud, lng = ~longitud, popup = ~barrio)
Si en vez de “pines” preferimos señalar las posiciones con puntos usamos addCircleMarkers() en lugar de addMarkers():
> leaflet(siniestros_viales) %>%
+ addTiles() %>%
+ addCircleMarkers(lat = ~latitud, lng = ~longitud, popup = ~barrio)
Digamos que nos interesa mostrar la variable “tipo”, que es categórica: distingue entre siniestros que causaron muertes y aquellos que sólo provocaron lesiones. Para crearle una paleta, dado que se trata de una variable categórica, debemos usar:
> paleta <- colorFactor(palette = "Set1", domain = siniestros_viales$tipo)
Y luego usamos la paleta ad-hoc en nuestro mapa, asignando paleta(tipo) al parámetro “color” :
> leaflet(siniestros_viales) %>%
+ addTiles() %>%
+ addCircleMarkers(lat = ~latitud, lng = ~longitud, popup = ~tipo,
+ color = ~paleta(tipo))
También podemos mostrar valores numérico usando el tamaño de los círculos, haciendo que varíen en tamaño para mostrar distintas cantidades. Aquí hay un ligero problema, y es que leaflet es literal con el tamaño: si en una fila la variable a mostrar toma el valor “10”, su círculo se dibujará en pantalla con unos 10 píxeles de radio. Si el valor es “10.000”, los círculos tendrán ese radio aproximado en píxeles y ya ni siquiera entrarán en una pantalla normal. Por eso, al igual que con los colores, suele ser necesario crear alguna escala ad-hoc. En nuestro caso, la única columna que indica cantidades es… “cantidad”, que registra el número de personas fallecidas en cada incidente. Los única valores que toma son “NA” (sin fallecidos), 1, ó 2. Para una puesta en pantalla razonable, podemos asignar al parámetro radius el resultado de cantidad * 10:
> leaflet(siniestros_viales) %>%
+ addTiles() %>%
+ addCircleMarkers(lat = ~latitud, lng = ~longitud, popup = ~paste("víctimas fatales:", as.character(cantidad)),
+ color = ~paleta(tipo), radius = ~cantidad * 10)
Los siniestros viales sin víctimas fatales, al tener vacía la columna “cantidad” aparecen de tamaño ínfimo, casi invisibles. Prueben jugar con el valor de “radius”, asignado por ejempo cantidad (sin multiplicar) para ver que pasa.
También es útil agregar una leyenda que explique la codificación de los datos. leaflet sólo permite mostrar leyendas basadas en color (no en el tamaño de los círculos), pero algo es algo. Agregamos la leyenda con addLegend(), especificando su título, la columna que contiene los valores, y la paleta que los representa:
> leaflet(siniestros_viales) %>%
+ addTiles() %>%
+ addCircleMarkers(lat = ~latitud, lng = ~longitud, popup = ~paste("víctimas fatales:", as.character(cantidad)),
+ color = ~paleta(tipo), radius = ~cantidad * 10) %>%
+ addLegend(title = "Tipo de siniestro vial", pal = paleta, values = ~tipo)
ggplotAhora, vamos a mostrar un ejemplo de cómo implementar interactividad en gráficos de ggplot con el paquete plotly. Vamos a activarlo:
> library(plotly)
Ahora solo necesitamos dos cosas:
Y eso es todo. Vamos a intentarlo haciendo un gráfico de cómo evoluciona la relación entre cantidad de víctimas/homicidio por provincia a lo largo de los años:
> library(tidyverse)
>
> df <- readxl::read_excel('./data/Integrado_HD_base_usuaria_2017-2020.xlsx')
>
> vict_prov_anio <- df %>% select(anio, provincia, Id_hecho, cant_vic) %>%
+ unique() %>% #Borro los valores duplicados, de manera que cada fila es un homicidio
+ group_by(anio, provincia) %>%
+ summarise(n = n(), #Cantidad de hechos
+ tot_vic = sum(cant_vic))%>% #Cantidad de víctimas
+ ungroup()%>%
+ mutate(tasa_vic = tot_vic/n)
>
> head(vict_prov_anio)
## # A tibble: 6 × 5
## anio provincia n tot_vic tasa_vic
## <dbl> <chr> <int> <dbl> <dbl>
## 1 2017 BUENOS AIRES 948 990 1.04
## 2 2017 CATAMARCA 15 16 1.07
## 3 2017 CHACO 59 61 1.03
## 4 2017 CHUBUT 43 43 1
## 5 2017 CIUDAD DE BUENOS AIRES 135 143 1.06
## 6 2017 CORDOBA 113 115 1.02
>
> p <- ggplot(vict_prov_anio, aes(x = anio, y = n, color = provincia, size = tasa_vic))+
+ geom_point()+
+ scale_y_log10()+
+ scale_color_viridis_d(option = "inferno")
>
> ggplotly(p)
Estos pasos los podemos replicar para casi cualquier gráfico de ggplot. Ahora que tenemos las herramientas necesarias para hacer gráficos interactivos, pasemos a la parte práctica.